package org.jeecg.common.security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA加密
 * @author: qiang.zhou
 * @date: 2024/6/3 14:35
 **/
@Component
@ConditionalOnProperty(name = "tender.data-security.type", havingValue = "RSA", matchIfMissing = true)
public class RSADataSecurityImpl implements DataSecurity, InitializingBean {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final String ALGORITHM = "RSA";

    @Value("${tender.data-security.rsa.publicKey:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC02XCYa+1RsRt9uaCAp1ZgHCmk79A/0xWb4V7fh1zpWKjhIjx0UElJeIT7FcD6AS8MnqCbq5aQXhxxfumF04JWbOv7aagYCcVrT3wccJ50Achu6OCpDBgFDPf7cmTGpMiLYEO1oxOL1D4HM0m6qnSTNqVh1aGXllrcQ4UnbpdEYQIDAQAB}")
    private String rsaPublicKey;

    @Value("${tender.data-security.rsa.privateKey:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALTZcJhr7VGxG325oICnVmAcKaTv0D/TFZvhXt+HXOlYqOEiPHRQSUl4hPsVwPoBLwyeoJurlpBeHHF+6YXTglZs6/tpqBgJxWtPfBxwnnQByG7o4KkMGAUM9/tyZMakyItgQ7WjE4vUPgczSbqqdJM2pWHVoZeWWtxDhSdul0RhAgMBAAECgYAtjF45ZWVeH9cmsSvlez/b8PvkGKpAiO92VSrJ7ZPPCY9vNGo5eUccBAiqHG6ACPBKo2EcE/lV3v4W77ntBLpckGpVC2jvhS3jnn0ZTe7csD3ktXZfkb7Cs4Ejm+8Yt3exjN3LKF4gumB4gj3khlMBOXQyyxNUOAOavKnlSJuwiQJBAPqA8cwB9515CzQvSWi2AT8cmJJKQPmuY5lEVGWCh1eNNmk/Ous5WZCb+vHGJiw/hdWrUr4GW0zvS5wx4/ArLksCQQC40UDtPnpIiuW7EWUlP9mqGFurAShzN8azomtyNkSI/8J0ELtEKZaKdjw7Nf1VfI7gSa5S+ZNGOltzqzQRzzyDAkEA4nA2ogvRMdad6xEnkXCcVdlajTpz237o5z2d8Jh6EQlD+wU+FC0mKhLlVzGfhfXx28BfLgh+9V+GAG4Uth/CWwJAWD9qjh3AC/VunnU+45tqVoLM+olSBDoEwe9y+JUbAoOFGSCIGpL1E+ZC+k7cST5xhn0IJQyQmv7wffmoojxe+wJBAN5VCGdBV8IbC5ykgEYgm9M9r+33L+yro53ITcHhl6u6hePyC/nU7Gl6ceV1j0KpzrDMbHWsuaYAv4513/uQIw8=}")
    private String rsaPrivateKey;

    private KeyPair keyPair;

    public RSADataSecurityImpl() {
    }

    @Override
    public byte[] encrypt(byte[] data) {
        try {
            Cipher ci = Cipher.getInstance("RSA");
            ci.init(1, this.keyPair.getPublic());
            // 需加密的字符长度, 偏移量
            int inputLen = data.length, offLen = 0;
            int i = 0;
            ByteArrayOutputStream bops = new ByteArrayOutputStream();
            while(inputLen - offLen > 0){
                byte [] cache;
                if(inputLen - offLen > 117){
                    cache = ci.doFinal(data, offLen, 117);
                }else{
                    cache = ci.doFinal(data, offLen,inputLen - offLen);
                }
                bops.write(cache);
                i++;
                offLen = 117 * i;
            }
            bops.close();
            return bops.toByteArray();
        } catch (Exception e) {
            throw new JeecgBootException("加密数据出错", e);
        }
    }

    @Override
    public byte[] decrypt(byte[] data) {
        try {
            Cipher ci = Cipher.getInstance("RSA");
            ci.init(2, this.keyPair.getPrivate());
            return ci.doFinal(data);
        } catch (InvalidKeyException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException var3) {
            throw new JeecgBootException("解密数据出错", var3);
        }
    }

    @Override
    public String getKey() {
        return this.rsaPublicKey;
    }

    @Override
    public String encrypt(String plaintext) {
        byte[] data = plaintext.getBytes(DEFAULT_CHARSET);
        byte[] encryptedData = this.encrypt(data);
        return Base64Utils.encodeToString(encryptedData);
    }

    @Override
    public String decrypt(String hexString) {
        byte[] encryptedData = Base64Utils.decodeFromString(hexString);
        byte[] data = this.decrypt(encryptedData);
        return new String(data, DEFAULT_CHARSET);
    }

    @Override
    public BigInteger getModulus() {
        return ((RSAPublicKey)this.keyPair.getPublic()).getModulus();
    }

    @Override
    public BigInteger getPublicExponent() {
        return ((RSAPublicKey)this.keyPair.getPublic()).getPublicExponent();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider());
        PrivateKey privateKey = this.parsePrivateKey(keyFactory);
        PublicKey publicKey = this.parsePublicKey(keyFactory);
        this.keyPair = new KeyPair(publicKey, privateKey);
    }

    private PublicKey parsePublicKey(KeyFactory keyFactory) throws InvalidKeySpecException {
        X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(Base64Utils.decodeFromString(this.rsaPublicKey));
        return keyFactory.generatePublic(bobPubKeySpec);
    }

    private PrivateKey parsePrivateKey(KeyFactory keyFactory) throws InvalidKeySpecException {
        PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Utils.decodeFromString(this.rsaPrivateKey));
        return keyFactory.generatePrivate(priPKCS8);
    }

}
